﻿@using Gurux.DLMS.AMI.Components.ContextMenu
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using System.Reflection;

@typeparam TItem
@implements IGXTable

@inject IGXNotifier Notifier
<CascadingValue Value="this">
    @if (Filter && FilterContent != null)
    {
        @if (ShowAllUsers)
        {
            <div class="form-check form-switch">
                <input class="form-check-input" type="checkbox"
                       onchange="@((ChangeEventArgs __e) => ShowAllUserData = @Convert.ToBoolean(__e.Value))" />
                <label>@Properties.Resources.ShowDataForAllUsers</label>
            </div>
        }
        @if (ShowRemoved)
        {
            <div class="form-check form-switch">
                <input class="form-check-input" type="checkbox"
                       onchange="@((ChangeEventArgs __e) => Removed = @Convert.ToBoolean(__e.Value))" />
                <label>@ShowRemovedMessage</label>
            </div>
        }
    }
    @if (Filter && FilterContent != null)
    {
        <MenuControl>
            <ChildContent>
                <MenuItem Text="@Properties.Resources.Search" Icon="oi oi-magnifying-glass" OnClick="@Search" />
            </ChildContent>
        </MenuControl>
        <br />
    }
    <table class="@CssClass">
        <thead>
            @if (Filter && FilterContent != null)
            {
                <tr>
                    @if (SelectionMode == SelectionMode.Multiple)
                    {
                        <th width="1%"></th>
                    }
                </tr>
                <tr>
                    @if (SelectionMode == SelectionMode.Multiple)
                    {
                        <th width="1%"></th>
                    }
                    @FilterContent
                </tr>
            }
            @if (HeaderContent != null)
            {
                <tr>
                    @if (SelectionMode == SelectionMode.Multiple)
                    {
                        <th width="1%">
                            <input class="form-check-input" type="checkbox"
                                   checked="@IsAllSelected()"
                                   onchange="@((ChangeEventArgs __e) => ToggleAllRow(Convert.ToBoolean(__e.Value)))" />
                        </th>
                    }
                    @HeaderContent
                    @if (MenuContent != null)
                    {
                        <th width="1%"></th>
                    }
                </tr>
            }
        </thead>
        <tbody>
            @foreach (TItem it in @GetItems())
            {
                @if (ItemContent != null)
                {
                    <tr class=@(GetSelectedClass(it)) onclick="@(()=>SelectRow(it))">
                        @if (SelectionMode == SelectionMode.Multiple)
                        {
                            <td>
                                <input class="form-check-input" type="checkbox"
                                       disabled="@IsCheckboxDisabled(it)"
                                       checked="@IsSelected(it)"
                                       onchange="@((ChangeEventArgs __e) => ToggleRow(it, Convert.ToBoolean(__e.Value)))" />
                            </td>
                        }
                        @ItemContent(it)
                        @if (Menu && MenuContent != null)
                        {
                            @if (it != null && it.Equals(Active))
                            {
                                <td>
                                    <ContextMenuController>
                                        @MenuContent
                                    </ContextMenuController>
                                </td>
                            }
                            else
                            {
                                <td>&nbsp;</td>
                            }
                        }
                    </tr>
                }
            }
        </tbody>
    </table>
    <!--Add pagination.-->
    @if (Pages.Count > 1 && !ShowAllData)
    {
        <ul class="pagination justify-content-center">
            @if (CurrentPage != 0)
            {
                <li onclick="@(() => SelectPage(0))"
                    style="cursor: pointer;"
                    class="page-link" href="#">&laquo;</li>
            }
            @foreach (int pos in Pages)
            {
                @if (pos >= CurrentPage - Spread && pos <= CurrentPage + Spread)
                {
                    <li onclick="@(() => SelectPage(pos))"
                        style="cursor: pointer;"
                        class="page-item @(CurrentPage == pos ? "active" : null)">
                        <span class="page-link" href="#">@(1 + pos)</span>
                    </li>
                }

            }
            @if (CurrentPage != TotalPages - 1)
            {
                <li onclick="@(() => SelectPage(TotalPages - 1))"
                    style="cursor: pointer;"
                    class="page-link" href="#">&raquo;</li>
            }
        </ul>
    }
    @if (!ShowAllData)
    {
        <div style="float: right;">
            <label>@Properties.Resources.RowsPerPage</label>
            <select class="select" value="@PageSize"
                    onchange="@((ChangeEventArgs e) => SetPageSize(e))">
                @foreach (var it in RowsPerPage)
                {
                    <option value="@it">@it</option>
                }
            </select>
        </div>
    }
    @if (Total)
    {
        <label>
            @if (@TotalCount == -1)
            {
                @(Properties.Resources.Total + " " + Properties.Resources.Unknown)
            }
            else
            {
                @(Properties.Resources.Total + " " + TotalCount.ToString("N0"))
            }
        </label>
    }
    @ChildContent
</CascadingValue>

@code {
    int[] RowsPerPage = new int[] { 10, 20, 50, 100 };
    public delegate Task SearchDelegate();

    /// <summary>
    /// User has modify the sorting.
    /// </summary>
    /// <param name="sort">Sort mode.</param>
    /// <param name="column">Sort column.</param>
    public delegate void SortChangedEventHandler(SortMode sort, string column);

    /// <summary>
    /// User has select the item.
    /// </summary>
    /// <param name="item">Selected item.</param>
    public delegate void SelectionChangedEventHandler(TItem item);

    /// <summary>
    /// Notify client to ask is checkbox enabled.
    /// </summary>
    /// <param name="item">Item</param>
    /// <param name="disabled">True, if checkbox is disabled.</param>
    /// <param name="check"></param>
    public delegate void CheckboxDisabledEventHandler(TItem item, out bool disabled, out bool check);

    /// <summary>
    /// Are removed items shown for the admin.
    /// </summary>
    [Parameter]
    public bool ShowRemoved { get; set; } = true;

    /// <summary>
    /// Data from the all users is shown for the admin.
    /// </summary>
    [Parameter]
    public bool ShowAllUsers { get; set; } = true;

    /// <summary>
    /// Data from the all users is shown for the admin.
    /// </summary>
    protected bool ShowAllUserData { get; set; } = false;

    /// <summary>
    /// All table rows are shown and pagination is not used.
    /// </summary>
    [Parameter]
    public bool ShowAllData { get; set; }

    /// <summary>
    /// Get removed items.
    /// </summary>
    public bool Removed { get; set; }

    /// <summary>
    /// Is total shown.
    /// </summary>
    [Parameter]
    public bool Total { get; set; } = true;

    /// <summary>
    /// Is filter shown.
    /// </summary>
    [Parameter]
    public bool Filter { get; set; } = true;

    /// <summary>
    /// Is menu shown.
    /// </summary>
    [Parameter]
    public bool Menu { get; set; } = true;

    /// <summary>
    /// User has modify the sorting.
    /// </summary>
    [Parameter]
    public SortChangedEventHandler? SortChanged { get; set; }

    /// <summary>
    /// User has select the new row.
    /// </summary>
    [Parameter]
    public SelectionChangedEventHandler? OnRowChanged { get; set; }

    /// <summary>
    /// User has select the new cell.
    /// </summary>
    [Parameter]
    public SelectionChangedEventHandler? OnCellChanged { get; set; }

    /// <summary>
    /// Notify client to ask is checkbox enabled.
    /// </summary>
    [Parameter]
    public CheckboxDisabledEventHandler? OnCheckboxDisabled { get; set; }

    /// <summary>
    /// User has modify the sorting.
    /// </summary>
    [Parameter]
    public SearchDelegate? OnSearch { get; set; }

    /// <summary>
    /// Show removed checkbox message.
    /// </summary>
    [Parameter]
    public string ShowRemovedMessage { get; set; } = Properties.Resources.ShowRemoved;

    /// <summary>
    /// Search.
    /// </summary>
    private void Search()
    {
        _currentPage = 0;
        if (OnSearch != null)
        {
            OnSearch();
        }
    }

    /// <summary>
    /// How many page links there are before and after the selected page.
    /// </summary>
    [Parameter]
    public int Spread { get; set; } = 2;

    private List<int> Pages = new List<int>();

    int _currentPage = 0;
    /// <summary>
    /// Focused item.
    /// </summary>
    public TItem? Active;

    /// <summary>
    /// Selected items.
    /// </summary>
    private SortedList<object, TItem> _selectedItems = new SortedList<object, TItem>();

    /// <summary>
    /// Selected items.
    /// </summary>
    public IEnumerable<TItem> SelectedItems
    {
        get
        {
            return _selectedItems.Values;
        }
    }

    /// <summary>
    /// Returns selected items or active item if there are no selected items.
    /// </summary>
    public IEnumerable<TItem> SingleOrDefault()
    {
        if (_selectedItems.Values.Any())
        {
            return _selectedItems.Values;
        }
        if (Active != null)
        {
            return new TItem[] { Active };
        }
        return new TItem[0];
    }

    /// <summary>
    /// Current page.
    /// </summary>
    [Parameter]
    public int CurrentPage
    {
        get
        {
            return _currentPage;
        }
        set
        {
            if (value < 0)
            {
                //Select the first page.
                value = 0;
            }
            else if (TotalCount == -1)
            {
                //Select the last page.
                if (value == _currentPage)
                {
                    value = 1 + _currentPage;
                }
            }
            else if (_pageSize * value > TotalCount)
            {
                //Select the last page.
                value = TotalCount / _pageSize;
            }
            _currentPage = value;
        }
    }

    int _pageSize = 10;

    /// <summary>
    /// Page size.
    /// </summary>
    public int PageSize
    {
        get
        {
            return _pageSize;
        }
        set
        {
            if (_pageSize < 10)
            {
                _pageSize = 10;
            }
            if (_pageSize != value)
            {
                _pageSize = value;
                Notifier.RowsPerPage = value;
            }
        }
    }

    private void SetPageSize(ChangeEventArgs e)
    {
        PageSize = Convert.ToInt32(e.Value);
        SelectPage(0).Start();
    }

    /// <summary>
    /// The total row count.
    /// </summary>
    public int TotalCount { get; private set; }

    /// <summary>
    /// The total page count.
    /// </summary>
    public int TotalPages
    {
        get
        {
            if (TotalCount == -1)
            {
                return 2 + CurrentPage;
            }
            return (int)Math.Ceiling(TotalCount / (double)PageSize);
        }
    }

    /// <summary>
    /// Change pagination page.
    /// </summary>
    /// <param name="index">Page index</param>
    private async Task SelectPage(int index)
    {
        CurrentPage = index;
        await RefreshDataAsync(true);
    }

    /// <summary>
    /// Available columns.
    /// </summary>
    [Parameter]
    public string[]? Columns { get; set; }

    /// <summary>
    /// Is edit allowed.
    /// </summary>
    [Parameter]
    public bool CanEdit { get; set; } = true;

    /// <summary>
    /// Selection mode.
    /// </summary>
    [Parameter]
    public SelectionMode SelectionMode { get; set; } = SelectionMode.Single;

    private CancellationTokenSource? _cts;

    private IEnumerable<TItem> GetItems()
    {
        if (Items == null)
        {
            return new TItem[0];
        }
        if (!string.IsNullOrEmpty(OrderBy))
        {
            if (SortMode == SortMode.Ascending)
            {
                System.Reflection.PropertyInfo? prop = typeof(TItem).GetProperty(OrderBy);
                if (prop != null)
                {
                    return Items.OrderBy(o => prop.GetValue(o, null));
                }
            }
            else if (SortMode == SortMode.Descending)
            {
                System.Reflection.PropertyInfo? prop = typeof(TItem).GetProperty(OrderBy);
                if (prop != null)
                {
                    return Items.OrderByDescending(o => prop.GetValue(o, null));
                }
            }
        }
        return Items;
    }

    public IEnumerable<TItem>? Items { get; private set; }

    /// <summary>
    /// Gets or sets the function providing items to the list.
    /// </summary>
    [Parameter]
    public GXItemsProviderDelegate<TItem>? ItemsProvider { get; set; }

    [Parameter]
    public string? Style { get; set; }

    [Parameter]
    public string? CssClass { get; set; } = "table table-striped table-responsive";

    [Parameter(CaptureUnmatchedValues = true)]
    public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }

    /// <summary>
    /// Filter content.
    /// </summary>
    [Parameter]
    public RenderFragment? FilterContent { get; set; }

    /// <summary>
    /// Menu content.
    /// </summary>
    [Parameter]
    public RenderFragment? MenuContent { get; set; }

    /// <summary>
    /// Header content.
    /// </summary>
    [Parameter]
    public RenderFragment? HeaderContent { get; set; }

    /// <summary>
    /// Gets or sets the item template for the list.
    /// </summary>
    [Parameter]
    public RenderFragment<TItem>? ChildContent { get; set; }

    /// <summary>
    /// Gets or sets the item template for the list.
    /// </summary>
    [Parameter]
    public RenderFragment<TItem>? ItemContent { get; set; }

    /// <summary>
    /// Remove items.
    /// </summary>
    /// <param name="items">Removed items.</param>
    public void RemoveItems(IEnumerable<TItem> items)
    {
        if (Items != null && items != null && items.Any())
        {
            PropertyInfo? pi = typeof(TItem).GetProperty("Id");
            if (pi != null)
            {
                bool updated = false;
                List<TItem> tmp = new List<TItem>(Items);
                foreach (var removed in items)
                {
                    foreach (var it in tmp)
                    {
                        object? value = pi.GetValue(removed);
                        if (value != null && value.Equals(pi.GetValue(it)))
                        {
                            --TotalCount;
                            _selectedItems.Remove(it);
                            tmp.Remove(it);
                            updated = true;
                            break;
                        }
                    }
                }
                if (updated)
                {
                    Items = tmp;
                    Pages.Clear();
                    if (!ShowAllData)
                    {
                        for (int pos = 0; pos != TotalPages; ++pos)
                        {
                            Pages.Add(pos);
                        }
                    }
                    StateHasChanged();
                }
            }
        }
    }

    /// <summary>
    /// Read values async.
    /// </summary>
    /// <param name="renderOnSuccess">Is UI render after success operation.</param>
    public async Task RefreshDataAsync(bool renderOnSuccess)
    {
        _cts?.Cancel();
        _cts = new CancellationTokenSource();
        CancellationToken cancellationToken = _cts.Token;
        GXItemsProviderRequest req = new GXItemsProviderRequest(ShowAllData ? 0 : CurrentPage * PageSize,
            ShowAllData ? 0 : PageSize,
            ShowAllUserData,
            Removed,
            OrderBy,
            SortMode == SortMode.Descending,
            null,
            cancellationToken);
        try
        {
            if (ItemsProvider == null)
            {
                throw new Exception("ItemsProvider not set.");
            }
            var result = await ItemsProvider(req);
            Items = result.Items;
            //Update selected items.
            List<TItem> list = new List<TItem>();
            if (Items != null)
            {
                foreach (var it in _selectedItems)
                {
                    foreach (var it2 in Items)
                    {
                        if (Compare(it.Value, it2))
                        {
                            list.Add(it2);
                            break;
                        }
                    }
                }
            }
            _selectedItems.Clear();
            foreach (var it in list)
            {
                object? value = it.GetType().GetProperty("Id")?.GetValue(it);
                if (value != null)
                {
                    _selectedItems.Add(value, it);
                }
            }
            //Select active item again.
            if (Active != null)
            {
                TItem active = Active;
                Active = default;
                if (Items != null)
                {
                    foreach (var it in Items)
                    {
                        if (Compare(active, it))
                        {
                            Active = it;
                            break;
                        }
                    }
                }
            }
            TotalCount = result.TotalItemCount;
            Pages.Clear();
            if (!ShowAllData)
            {
                for (int pos = 0; pos != TotalPages; ++pos)
                {
                    Pages.Add(pos);
                }
            }
            // Only apply result if the task was not canceled.
            if (!cancellationToken.IsCancellationRequested && renderOnSuccess)
            {
                StateHasChanged();
            }
        }
        catch (Exception ex)
        {
            if (ex is OperationCanceledException oce && oce.CancellationToken == cancellationToken)
            {
                // Exception is ignored on cancel.
            }
            else
            {
                Notifier?.ProcessError(ex);
            }
        }
    }

    /// <inheritdoc/>
    [Parameter]
    public string? OrderBy { get; set; }

    /// <inheritdoc/>
    [Parameter]
    public SortMode SortMode { get; set; } = SortMode.None;

    /// <inheritdoc/>

    public void NotifyShortChange()
    {
        if (SortChanged != null && !string.IsNullOrEmpty(OrderBy))
        {
            SortChanged(SortMode, OrderBy);
        }
        StateHasChanged();
    }

    protected string GetSelectedClass(TItem? active)
    {
        object? current = null, value = null;
        if (active != null)
        {
            value = active.GetType().GetProperty("Id")?.GetValue(active);
        }
        if (Active != null)
        {
            current = Active.GetType().GetProperty("Id")?.GetValue(Active);
        }
        return value != null && value.Equals(current) ? "table-info" : "table-striped";
    }

    /// <inheritdoc/>
    public void SelectRow(object selected)
    {
        Active = (TItem)selected;
        OnRowChanged?.Invoke((TItem)selected);
        StateHasChanged();
    }

    /// <inheritdoc/>
    public void SelectCell(object selected)
    {
        Active = (TItem)selected;
        OnRowChanged?.Invoke((TItem)selected);
        OnCellChanged?.Invoke((TItem)selected);
        StateHasChanged();
    }

    /// <summary>
    /// Are all rows selected.
    /// </summary>
    /// <returns>True, if all the rows are selected.</returns>
    protected string? IsAllSelected()
    {
        if (Items != null && _selectedItems.Count == Items.Count() && Items.Any())
        {
            return "checked";
        }
        return null;
    }

    private string? IsCheckboxDisabled(TItem target)
    {
        bool disabled = false;
        bool check = false;
        if (OnCheckboxDisabled != null)
        {
            OnCheckboxDisabled.Invoke(target, out disabled, out check);
        }
        if (check)
        {
            ToggleRow(target, check);
        }
        if (!disabled)
        {
            return null;
        }
        return "disabled";
    }

    /// <summary>
    /// Is row selected.
    /// </summary>
    /// <param name="selected">Selected item.</param>
    /// <returns>True, if the row is selected.</returns>
    protected string? IsSelected(TItem? selected)
    {
        bool ret = false;
        if (selected != null)
        {
            object? value = selected.GetType().GetProperty("Id")?.GetValue(selected);
            if (value != null)
            {
                ret = _selectedItems.ContainsKey(value);
            }
        }
        if (ret)
        {
            return "checked";
        }
        return null;
    }


    private static bool Compare(TItem? item1, TItem? item2)
    {
        if (item1 == null || item2 == null)
        {
            return false;
        }
        var prop = item1.GetType().GetProperty("Id");
        object? value1 = prop?.GetValue(item1);
        object? value2 = prop?.GetValue(item2);
        return value1 == null ? false : value1.Equals(value2);
    }

    /// <summary>
    /// Select or de-select all rows.
    /// </summary>
    /// <param name="select">Selected item</param>
    protected void ToggleAllRow(bool select)
    {
        if (Items != null)
        {
            foreach (var it in Items)
            {
                object? value = it.GetType().GetProperty("Id")?.GetValue(it);
                if (value != null)
                {
                    if (!select && _selectedItems.ContainsKey(value))
                    {
                        _selectedItems.Remove(value);
                    }
                    else if (select && !_selectedItems.ContainsKey(value))
                    {
                        _selectedItems.Add(value, it);
                    }
                }
            }
        }
    }

    /// <summary>
    /// Select or de-select the row.
    /// </summary>
    /// <param name="selected">Selected item</param>
    /// <param name="select">Is item selected or deselected.</param>
    protected void ToggleRow(TItem? selected, bool select)
    {
        if (selected != null)
        {
            object? value = selected.GetType().GetProperty("Id")?.GetValue(selected);
            if (value != null)
            {
                if (!select && _selectedItems.ContainsKey(value))
                {
                    _selectedItems.Remove(value);
                }
                else if (select && !_selectedItems.ContainsKey(value))
                {
                    _selectedItems.Add(value, selected);
                }
            }
        }
    }

    /// <summary>
    /// Is column hidden.
    /// </summary>
    /// <param name="name">Name of the column.</param>
    /// <returns>True, if table column is hidden.</returns>
    bool IGXTable.IsHidden(string? name)
    {
        if (string.IsNullOrEmpty(name) || Columns == null || !Columns.Any())
        {
            return false;
        }
        return !Columns.Contains(name);
    }

    ///<summary>
    ///Read table data.
    ///</summary>
    protected override async Task OnInitializedAsync()
    {
        _pageSize = Notifier.RowsPerPage;
        await RefreshDataAsync(false);
    }

    /// <inheritdoc />
    public void Dispose()
    {
        _cts?.Cancel();
    }
}